package parser

import (
	"fmt"
	"github.com/fatih/color"
	"github.com/hashicorp/hcl/v2"
	"github.com/hashicorp/hcl/v2/gohcl"
	"github.com/hashicorp/hcl/v2/hclsyntax"
	"github.com/ihaiker/tfwrapper/internal/config"
	"github.com/ihaiker/tfwrapper/internal/errors"
	"github.com/ihaiker/tfwrapper/internal/eval"
	"github.com/ihaiker/tfwrapper/internal/logger"
	"github.com/ihaiker/tfwrapper/internal/util"
	"path"
	"path/filepath"
)

func parseModule(options config.Running, file *hcl.File) ([]*config.Module, hcl.Diagnostics) {
	log := logger.Logger(options)

	content, _, diags := file.Body.PartialContent(&hcl.BodySchema{
		Blocks: []hcl.BlockHeaderSchema{
			{Type: "module", LabelNames: []string{"name"}},
		},
	})
	if diags.HasErrors() || len(content.Blocks) == 0 {
		return nil, diags
	}
	ctx, diags := eval.CreateEvalContext(options)
	if diags.HasErrors() {
		return nil, diags
	}

	modules := make([]*config.Module, len(content.Blocks))
	for idx := 0; idx < len(modules); idx++ {
		module := new(config.Module)
		module.Range = content.Blocks[idx].DefRange
		blockBody := content.Blocks[idx].Body
		diags = gohcl.DecodeBody(blockBody, ctx.EvalContext, module)
		diags = errors.Filter(diags, errors.Not(errors.IsDependsOnUnSupported))
		if diags.HasErrors() {
			return nil, diags
		}
		module.Name = content.Blocks[idx].Labels[0]

		log.Debugf("found module: %s", color.HiMagentaString(module.Name))

		//解析 depends_on 属性
		if dependsOn, has := blockBody.(*hclsyntax.Body).Attributes["depends_on"]; has {
			for _, traversal := range dependsOn.Expr.Variables() {
				r := dependsOn.Range()
				if len(traversal) != 2 || traversal.RootName() != "module" {
					return nil, diags.Append(&hcl.Diagnostic{
						Severity: hcl.DiagError,
						Summary:  "Unrecognized parameter",
						Detail: fmt.Sprintf("Unrecognized dependency %s in module that named %s",
							color.New(color.FgHiRed, color.Underline).Sprint(traversal.RootName()),
							color.HiGreenString(module.Name)),
						Subject: &r,
					})
				} else if tr, match := traversal[1].(hcl.TraverseAttr); !match {
					return nil, diags.Append(&hcl.Diagnostic{
						Severity: hcl.DiagError,
						Summary:  "Unrecognized parameter",
						Detail: fmt.Sprintf("Unrecognized dependency %s in module that named %s",
							color.New(color.FgHiRed, color.Underline).Sprint(traversal.RootName()),
							color.HiGreenString(module.Name)),
						Subject: &r,
					})
				} else {
					module.DependsOn = append(module.DependsOn, fmt.Sprintf("module.%s", tr.Name))
				}
			}
		}

		//从inputs中获取
		if module.Inputs != nil {
			var attrs hcl.Attributes
			if attrs, diags = module.Inputs.Body.JustAttributes(); util.HasErrors(diags) {
				return nil, diags
			}
			module.DependsOn = append(module.DependsOn, analyzeDependsOn(attrs)...)
		}
		module.DependsOn = util.SetSlice(module.DependsOn)
		modules[idx] = module
	}
	return modules, nil
}

func analyzeDependsOn(attrs hcl.Attributes) []string {
	dependsOn := make([]string, 0)
	for _, attr := range attrs {
		for _, traversal := range attr.Expr.Variables() {
			if traversal.RootName() != "module" {
				continue
			}
			if trAttr, has := traversal[1].(hcl.TraverseAttr); has {
				dependsOn = append(dependsOn, fmt.Sprintf("module.%s", trAttr.Name))
			}

		}
	}
	return dependsOn
}

func checkForDuplicateDefinitions(stackConfig config.StackConfig) hcl.Diagnostics {
	modules := map[string]*config.Module{}
	diags := hcl.Diagnostics{}
	for _, configFile := range stackConfig.Files {
		for _, module := range configFile.Modules {
			if m1, has := modules[module.Name]; has {
				diags = diags.Append(&hcl.Diagnostic{
					Severity: hcl.DiagError,
					Summary:  "Duplicate module definition",
					Detail: fmt.Sprintf("Duplicate definition of module %s in :\n%s\n%s \nthe module name must be globally unique.",
						color.HiGreenString(module.Name),
						color.HiRedString(m1.Range.String()),
						color.HiRedString(module.Range.String())),
				})
			} else {
				modules[module.Name] = module
			}
		}
	}
	return diags
}

// determineWhichModuleIsRunning 判断是否只运行固定的module，在module文件夹中运行命令
func determineWhichModuleIsRunning(wrapper *config.Wrapper) string {
	dir, _ := filepath.Rel(path.Dir(wrapper.Config.WrapperConfigPath), wrapper.Config.WorkingDir)
	for _, file := range wrapper.Stack.Files {
		for _, module := range file.Modules {
			if module.Source == dir {
				return module.Name
			}
		}
	}
	return ""
}
